using System;
using System.Diagnostics;
using System.Threading;
using System.Reflection;
using System.Windows.Forms;

namespace WillStott.MxAppToolbox
{
	/// <summary>
	/// Your Windows Forms application must contain a class derived from Program to benefit from many
	/// of the facilities provided by MxAppToolbox. The Program class is used to create the single 
	/// instance of an object concerned with application wide features and information. Program is an 
	/// abstract class because it goes further than providing just an interface; it also supplies 
	/// implementation common to applications built using MxAppToolbool. Take the following steps to
	/// implement a MxAppToolbox enabled Windows Forms application after creating the Windows Forms C# project:
	/// <list type="number">
	/// <item><description>Rename Form1 as MainForm and cut the program entry point, static void Main()</description></item>
	/// <item><description>Create a class for the application, say TestWinApp, derive it from Program and paste the program entry point and make MainForm the class passed to Application.Run</description></item>
	/// <item><description>Supply the concrete classes for Program's defined abstract classes</description></item>
	/// <item><description>Provide the private and static constructor as well as Instance- see example</description></item>
	/// <item><description>Copy TestWinAppMsg.xml into project's src directory and add it to the project as an existing item. Set it's build action to embedded resource</description></item>
	/// <item><description>Implement ReadRegistryValues and WriteRegistryValues + support them in Options dialog</description></item>
	/// </list>
	/// <example>
	/// <code>
    /// 
    /// System.Globalization;
    /// System.Reflection;
    /// System.Threading;
    /// 
	/// public class TestWinApp : Program
	///	{
	///		private bool         m_MainFormClosing;
	///		private UniMsgList   m_UniMsgList;
	///		private UniLog       m_UniEventLog;
    ///     private bool         m_VerboseLog;
    ///     private string       m_helpFilePathName;   
	///
	///		private TestWinApp() {}
	///		static TestWinApp()
	///		{
	///			try
	///			{
	///				Program.Instance = new TestWinApp();
	///				TestWinApp.Instance.m_MainFormClosing = true;	//reset in MainFrom when it is open
	///				
	///				TestWinApp.Instance.m_UniEventLog = new UniLog();
    ///             TestWinApp.Instance.m_VerboseLog = true;
	///				TestWinApp.Instance.m_UniEventLog.Name = "TestWinApp";
	///			}
	///			catch (Exception e)
	///			{		//unless you end the program here a TypeInitializationException is thrown as the ctor failed
	///				Program.FatalErrorAbend("TestApp.TestWinApp()", e.Message);
	///			}
	///		}
	///		static public new TestWinApp Instance {	get { return Program.Instance as TestWinApp; }}
	///		
    ///     public override bool	VerboseLog 
    ///     { 
    ///         get { return m_VerboseLog ; }
    ///         set { m_VerboseLog  = value;} 
    ///     }
    /// 
    ///	    public override string HelpFilePathName 
    ///	    { 
    ///	        get { return m_helpFilePathName; } 
    ///	        set { m_helpFilePathName = value; } 
    ///	    }
    /// 
	///		public override UniLog AppLog {	get { return m_UniEventLog; }}
	///		public override UniMsgList	ErrMsgs { get { return m_UniMsgList; }}	
    /// 
	///		public override bool MainFormClosing
	///		{ 
	///			get { return m_MainFormClosing; }
	///			set { m_MainFormClosing = value; }
	///		}
	///		public override bool CloseAllDocuments(bool AllowCancel)
	///		{
	///			bool rc = true;
	///			if ( (AllowCancel == true) AND (MessageBox.Show("Close now?", "Msg", MessageBoxButtons.YesNo) == DialogResult.No ))
	///				rc = false;
	///			return rc;
	///		}
    ///     public override void MainArgProc(string[] args)
    ///     {
    ///         if (args.Length > 0)
    ///         {
    ///            if (args[0] == "/culture=DE")
    ///                Thread.CurrentThread.CurrentUICulture = new CultureInfo("de");
    ///         }
    ///         Assembly resAsm = Program.GetResourceAssembly(this);
    ///         MxStdApp.Instance.m_UniMsgList = UniMsgList.LoadList(resAsm, "TestApp.TestWinAppMsg.xml");
    ///         HelpFilePathName = Utils.GetPath(resAsm.Location) + "TestApp.chm";
    ///     }
    /// 
    ///     [STAThread]
    ///     static void Main(string[] args)
    ///     {
    ///        MxStdApp.Instance.MainArgProc(args);
    ///        Application.Run(new MainForm());
    ///     }
	/// </code>
	/// </example>
	/// </summary>
	public abstract class Program
	{
		/// <summary>
		/// Get the UniLog object for logging facilities
		/// </summary>
		public abstract UniLog		AppLog { get; }

		/// <summary>
		/// Get, Set the verbose log flag. When set calls to Log() will result in an entry being
		/// made in the application event log.
		/// </summary>
		public abstract bool		VerboseLog { get; set; }

        /// <summary>
        /// Process any arguments passed to the application. This is also where you must load
        /// the resource list for UniMsgs
        /// </summary>
        public abstract void        MainArgProc(string[] args);

		/// <summary>
		/// Get, Set the flag to indicate when the MainForm is closing. It is set true at startup when
		/// Program.Instance is being set. It is set false by IMxDlgForm event handler when the MainForm has loaded. 
		/// This flag is used in Program.TerminateApp to decide whether or not to terminate the application
		/// by closing its Main Window.
		/// </summary>
		public abstract bool		MainFormClosing { get; set; }

		/// <summary>
		/// Get the UniMsgList object for message support
		/// </summary>
		public abstract UniMsgList	ErrMsgs { get; }

		/// <summary>
		/// Get the fill filename and path for the application help file. Typically it is the same
        /// directory as .exe, but may also be in foreign language sub directory
		/// </summary>
        public abstract string HelpFilePathName { get; set;}

        /// <summary>
        /// Get the Assembly containing the resources. Depending on the CurrentUICulture this
        /// may be the main assembly (.exe) or a satellite assembly
        /// </summary>
        public static Assembly GetResourceAssembly(object mainAsm)
        {
            Assembly asm = mainAsm.GetType().Assembly;
            try
            {
                if (Thread.CurrentThread.CurrentUICulture.Name != "en-US")
                    asm = asm.GetSatelliteAssembly(Thread.CurrentThread.CurrentUICulture);
            }
            catch (Exception)
            {
                m_instance.Log("Operating in unsupported culture - {0}", Thread.CurrentThread.CurrentUICulture.Name);
                asm = mainAsm.GetType().Assembly;   //ignore the exception, all resources always present in en-US form
            }
            return asm;
        }


        private   static Program m_instance = null;	//there must never been more than one derived class otherwise this value will be overwritten 

		/// <summary>
		/// Provide access to the application's single instance object from the derived class, say TestWinApp, 
		/// and from any classes within MxAppToolbox.
		/// </summary>
		protected internal static Program Instance
		{
			set 
			{ 
				try
				{
					System.Diagnostics.Debug.Assert(m_instance == null, "Program.Instance already initialised", "Check that there is only one class derived from Program");
					m_instance = value;
					m_instance.MainFormClosing = true;

					ThreadExceptionEventHandler handler = new ThreadExceptionEventHandler(Program.Instance.UniHandler);
					System.Windows.Forms.Application.ThreadException += handler;
				}
				catch(Exception efinal)
				{		//unless you end the program here a TypeInitializationException is thrown as this is called from the static ctor of the derived class which will otherwise fail
					Program.FatalErrorAbend("Program.Instance-set", efinal.Message);
				}
			}
			get 
			{ 
				if ( Application.AllowQuit )	    //stop any Forms that use Program.Instance crashing in DesignMode
				{
					System.Diagnostics.Debug.Assert(m_instance != null, "Program.Instance not initialised", "Check that derived class stores its sole instance in Program.Instance");
				}
				return m_instance; 
			}
		}
		/// <summary>
		/// Resets the application's documents
		/// </summary>
		/// <returns>True if all documents reset</returns>
		public virtual bool ResetAllDocuments()
		{
			return true;
		}

		/// <summary>
		/// Closes the application's documents, saving them as necessary. This is called
		/// when the application is forcibly terminating and when the MainForm closes. It
		/// may also be invoked when the user chooses to exit the program.
		/// </summary>
		/// <param name="AllowCancel">Allow user to cancel operation</param>
		/// <returns></returns>
		public virtual bool CloseAllDocuments(bool AllowCancel)
		{
			return true;
		}
		/// <summary>
		/// Invoked by application's error handling or when the user decides to close the Main window.
		/// The File | Exit action handler should invoke this method and put all closing
		/// operations in MainForm_Closing handler.
		/// </summary>
		/// <param name="KillProcess">Kill the process</param>
		public virtual void TerminateApp(bool KillProcess)
		{	//not tested by NUnit
			try
			{
				CloseAllDocuments(false);
				if (Program.Instance.MainFormClosing == false )
					Process.GetCurrentProcess().CloseMainWindow();
				else
				{
					if (KillProcess == true )
						Process.GetCurrentProcess().Kill();
					else
						Application.Exit();
				}
			}
			catch (Exception)
			{		//the program is ending, so finish the job
				Process.GetCurrentProcess().Kill();
			}
		}

		/// <summary>
		/// Invoked by error handling to reset the application to a know state
		/// </summary>
		public virtual void ResetApp()
		{
			ResetAllDocuments();
		}

		/// <summary>
		/// Override to provide an alternative message during FatalErrorAbend()
		/// </summary>
		/// <returns></returns>
		protected virtual string GetFatalErrorAbendMsg()
		{	
			return "";
		}

		/// <summary>
		/// FatalErrorAbend should only be invoked as a last resort - indicates the error handling is defective 
		/// or not yet initialised. Assuming something bad has happened, don't attempt to load message from resources
		/// </summary>
		/// <param name="Location">Where this method was invoked</param>
		/// <param name="Cause">The reason the method was invoked</param>
		static public void FatalErrorAbend( string Location, string Cause )
		{	//not tested by NUnit
			try
			{
				String Msg = "";
				if ( Program.Instance != null) 
					Msg = Program.Instance.GetFatalErrorAbendMsg();

				if (Msg.Length == 0  )
				{
					Msg = Location + ": " + Cause + Environment.NewLine;
					Msg += Environment.NewLine;
					Msg += "There is a problem with this program. It needs to be terminated as soon as possible." + Environment.NewLine;
					Msg += "If this problem persists, you should consider restarting your PC or re-installing this" + Environment.NewLine;
					Msg += "program. It could also be an indication that your computer needs some maintenance." + Environment.NewLine; 
					Msg += Environment.NewLine;
					Msg += "Tip: Further information about the problem may have been written to your PC's" + Environment.NewLine;
					Msg += "Application Event Log. Seek support if you require further assistance." + Environment.NewLine;
					Msg += Environment.NewLine;
					Msg += "______________________________________________________________________";
					Msg += Environment.NewLine;
					Msg += "Press 'Yes' to force the program to terminate, or 'No' to let it terminate normally";
				}
				if ( ( Program.Instance == null) || (MessageBox.Show(Msg, "Fatal Application Error", MessageBoxButtons.YesNo) == DialogResult.Yes ))
					Process.GetCurrentProcess().Kill();
				else
					Program.Instance.TerminateApp(true);
			}
			catch(Exception)
			{
			}
		}

		/// <summary>
		/// Exception handler for application's thread
		/// </summary>
		/// <param name="sender">object causing exception</param>
		/// <param name="args">arguments for exception</param>
		protected virtual void UniHandler(object sender, ThreadExceptionEventArgs args)
		{	//not tested by NUnit
			try
			{
				UniException  uni = args.Exception as UniException;
				if ( uni != null )
				{
					Program.Instance.AppLog.WriteMsg(EventLogEntryType.Error, uni.ToString());

					UniMsg msg = Program.Instance.ErrMsgs.GetMsg(uni.MsgId);
					UniMsgDlg dlg = new UniMsgDlg(msg, uni.ResetAction, uni.Class, uni.Fmt, uni.Args);
                    if (dlg.ShowDialog() == DialogResult.Yes)
					{
						if ( uni.ResetAction == UniException.Reset.TermApp )
							Program.Instance.TerminateApp(false);
						else
							Program.Instance.ResetApp();
					}
				}

			}
			catch (Exception e)
			{		//no point in throwing further exceptions as the Exception handler is not installed
				FatalErrorAbend("Program.UniHandler()", e.Message);
			}
		}

		/// <summary>
		/// Write message to application event log
		/// </summary>
		/// <param name="fmt">Format string</param>
		/// <param name="args">arguments</param>
		public virtual void Log(string fmt, params object[] args)
		{
			if ( m_instance.VerboseLog )
				Program.Instance.AppLog.WriteMsg(EventLogEntryType.Information, fmt, args);
		}
	}
}
